﻿using System;
using System.Collections;
using System.Collections.Generic;
using System.ComponentModel.DataAnnotations;
using System.Diagnostics.CodeAnalysis;
using System.Linq;
using System.Linq.Expressions;
using NQueries.Data.Queries;
using NQueries.Utils;

namespace NQueries.Data.Base
{
    /// <inheritdoc />
    [SuppressMessage("Microsoft.Design", "CA1005:AvoidExcessiveParametersOnGenericTypes")]
    [SuppressMessage("Microsoft.Naming", "CA1710:IdentifiersShouldHaveCorrectSuffix")]
    public abstract class BaseRepository<TContext, T, TBase> : IRepository<T>
        where TContext : IDataContext
        where T : class, TBase, new()
        where TBase : class, new()
    {
        /// <summary>
        /// Constructor.
        /// </summary>
        /// <param name="dataContext"></param>
        protected BaseRepository(TContext dataContext)
        {
            Check.IsNotNull(dataContext, "dataContext");

            DataContext = dataContext;
        }

        /// <summary>
        /// Gets the <see cref="IDataContext"/> for this repository.
        /// </summary>
        protected TContext DataContext { get; private set; }

        /// <summary>
        /// Gets the <see cref="IQueryable"/>.
        /// </summary>
        protected abstract IQueryable<T> Query { get; }

        #region IRepository<T> Members

        /// <inheritdoc />
        public T GetById(object id)
        {
            IQuery queryById = DataFactory.Current.GetIdQuery<T>();
            if (queryById != null)
                return (T)queryById.Invoke(DataContext, id);

            return Query.Single(ByIdExpression(id));
        }

        /// <inheritdoc />
        object IRepository.GetById(object id) { return GetById(id); }

        /// <inheritdoc />
        public Type ElementType { get { return Query.ElementType; } }

        /// <inheritdoc />
        public Expression Expression { get { return Query.Expression; } }

        /// <inheritdoc />
        public IQueryProvider Provider { get { return Query.Provider; } }

        /// <inheritdoc />
        public IEnumerator GetEnumerator() { return Query.GetEnumerator(); }

        /// <inheritdoc />
        IEnumerator<T> IEnumerable<T>.GetEnumerator() { return Query.GetEnumerator(); }

        /// <inheritdoc />
        public abstract void AddOnSubmit(T entity);

        /// <inheritdoc />
        public abstract void RemoveOnSubmit(T entity);

        /// <inheritdoc />
        public abstract void Update(T entity);

        /// <inheritdoc />
        public abstract void Attach(T entity);

        /// <inheritdoc />
        public abstract IQueryable<T> Include(string path);

        /// <inheritdoc />
        public IQueryable<T> GetSatisfying(ISpecification<T> specification)
        {
            Check.IsNotNull(specification, "specification");
            return this.Where(specification.Predicate);
        }

        /// <inheritdoc />
        public bool Exists(object id)
        {
            Check.IsNotNull(id, "id");
            return Query.Where(ByIdExpression(id)).Count() == 1;
        }

        /// <inheritdoc />
        public bool Validate(object entity)
        {
            List<ValidationResult> validationResults = new List<ValidationResult>();
            return Validate(entity, validationResults);
        }

        /// <inheritdoc />
        public bool Validate(object entity, ICollection<ValidationResult> validationResults)
        {
            Check.IsNotNull(entity, "entity");
            Check.IsNotNull(validationResults, "validationResults");
            ValidationContext validationContext = new ValidationContext(entity, null, null);
            return Validator.TryValidateObject(entity, validationContext, validationResults, true);
        }

        #endregion

        static Expression<Func<T, bool>> ByIdExpression(object id)
        {
            ParameterExpression itemParameter = Expression.Parameter(typeof(T), "item");
            return Expression.Lambda<Func<T, bool>>(Expression.Equal(Expression.Property(itemParameter, typeof(T).GetPrimaryKeyPropertyInfo().Name), Expression.Constant(id)), new[] { itemParameter });
        }
    }
}